﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

using StackExchange.Redis;

namespace RedisLib
{
    public partial class RedisClient
    {
        public async Task<bool> StringSet<T>(string key, T value, TimeSpan? expiry = null, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return false;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue redisValue = ConvertObjectValueToRedisValue(value);

            return await database.StringSetAsync(redisKey, redisValue, expiry);
        }

        public async Task<T?> StringSetAndGetForValueType<T>(string key, T value, TimeSpan? expiry = null, int db = -1, CancellationToken cancellationToken = default)
            where T : struct
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return default;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue redisValue = ConvertObjectValueToRedisValue(value);
            RedisValue oldRedisValue = await database.StringSetAndGetAsync(redisKey, redisValue, expiry);

            return ConvertRedisValueToValueTypeValue<T>(oldRedisValue);
        }

        public async Task<T> StringSetAndGet<T>(string key, T value, TimeSpan? expiry = null, int db = -1, CancellationToken cancellationToken = default)
            where T : class
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return default;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue redisValue = ConvertObjectValueToRedisValue(value);
            RedisValue oldRedisValue = await database.StringSetAndGetAsync(redisKey, redisValue, expiry);

            return ConvertRedisValueToObjectValue<T>(oldRedisValue);
        }

        public async Task<bool> StringSet<T>(Dictionary<string, T> keyValues, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return false;

            KeyValuePair<RedisKey, RedisValue>[] redisKeyValues = BatchConvertKeyValuesToRedisKeyValues(keyValues);

            return await database.StringSetAsync(redisKeyValues);
        }

        public async Task<T?> StringGetForValueType<T>(string key, int db = -1, CancellationToken cancellationToken = default)
            where T : struct
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return default;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue redisValue = await database.StringGetAsync(redisKey);

            return ConvertRedisValueToValueTypeValue<T>(redisValue);
        }

        public async Task<T?> StringGetSetForValueType<T>(string key, T newValue, int db = -1, CancellationToken cancellationToken = default)
            where T : struct
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return default;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue newRedisValue = ConvertObjectValueToRedisValue(newValue);
            RedisValue redisValue = await database.StringGetSetAsync(redisKey, newRedisValue);

            return ConvertRedisValueToValueTypeValue<T>(redisValue);
        }

        public async Task<KeyValuePair<string, T?>[]> StringGetForValueType<T>(List<string> keys, int db = -1, CancellationToken cancellationToken = default)
            where T : struct
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return default;

            RedisKey[] redisKeys = BatchConvertStringKeyToRedisKey(keys);
            RedisValue[] redisValues = await database.StringGetAsync(redisKeys);

            KeyValuePair<string, T?>[] values = new KeyValuePair<string, T?>[redisValues.Length];
            for (int i = 0; i < redisValues.Length; i++)
            {
                RedisValue redisValue = redisValues[i];
                string key = keys[i];
                T? value = ConvertRedisValueToValueTypeValue<T>(redisValue);
                values[i] = new KeyValuePair<string, T?>(key, value);
            }

            return values;
        }

        public async Task<T> StringGet<T>(string key, int db = -1, CancellationToken cancellationToken = default)
            where T : class
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return default;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue redisValue = await database.StringGetAsync(redisKey);

            return ConvertRedisValueToObjectValue<T>(redisValue);
        }

        public async Task<T> StringGetSet<T>(string key, T newValue, int db = -1, CancellationToken cancellationToken = default)
            where T : class
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return default;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue newRedisValue = ConvertObjectValueToRedisValue(newValue);
            RedisValue redisValue = await database.StringGetSetAsync(redisKey, newRedisValue);

            return ConvertRedisValueToObjectValue<T>(redisValue);
        }

        public async Task<KeyValuePair<string, T>[]> StringGet<T>(List<string> keys, int db = -1, CancellationToken cancellationToken = default)
            where T : class
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return default; //database.StringDecrementAsync

            RedisKey[] redisKeys = BatchConvertStringKeyToRedisKey(keys);
            RedisValue[] redisValues = await database.StringGetAsync(redisKeys);

            KeyValuePair<string, T>[] values = new KeyValuePair<string, T>[redisValues.Length];
            for (int i = 0; i < redisValues.Length; i++)
            {
                RedisValue redisValue = redisValues[i];
                string key = keys[i];

                if (redisValue == RedisValue.Null)
                {
                    values[i] = new KeyValuePair<string, T>(key, default);
                    continue;
                }

                T value = ConvertRedisValueToObjectValue<T>(redisValue);
                values[i] = new KeyValuePair<string, T>(key, value);
            }

            return values;
        }

        public async Task<long?> StringDecrement(string key, long value = 1L, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return null;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            return await database.StringDecrementAsync(redisKey, value);
        }

        public async Task<double?> StringDecrement(string key, double value, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return null;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            return await database.StringDecrementAsync(redisKey, value);
        }

        public async Task<long?> StringIncrement(string key, long value = 1L, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return null;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            return await database.StringIncrementAsync(redisKey, value);
        }

        public async Task<double?> StringIncrement(string key, double value, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return null;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            return await database.StringIncrementAsync(redisKey, value);
        }

        public async Task<T?> StringGetDeleteForValueType<T>(string key, int db = -1, CancellationToken cancellationToken = default)
            where T : struct
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return default;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue redisValue = await database.StringGetDeleteAsync(redisKey);

            return ConvertRedisValueToValueTypeValue<T>(redisValue);
        }

        public async Task<T> StringGetDelete<T>(string key, int db = -1, CancellationToken cancellationToken = default)
            where T : class
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return default;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue redisValue = await database.StringGetDeleteAsync(redisKey);

            return ConvertRedisValueToObjectValue<T>(redisValue);
        }

        public async Task<T?> StringGetSetExpiryForValueType<T>(string key, TimeSpan? expiry = null, int db = -1, CancellationToken cancellationToken = default)
            where T : struct
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return null;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue redisValue = await database.StringGetSetExpiryAsync(redisKey, expiry);

            return ConvertRedisValueToValueTypeValue<T>(redisValue);
        }

        public async Task<T?> StringGetSetExpiryForValueType<T>(string key, DateTime expiry, int db = -1, CancellationToken cancellationToken = default)
            where T : struct
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return null;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue redisValue = await database.StringGetSetExpiryAsync(redisKey, expiry);

            return ConvertRedisValueToValueTypeValue<T>(redisValue);
        }

        public async Task<T> StringGetSetExpiry<T>(string key, TimeSpan? expiry = null, int db = -1, CancellationToken cancellationToken = default)
            where T : class
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return null;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue redisValue = await database.StringGetSetExpiryAsync(redisKey, expiry);

            return ConvertRedisValueToObjectValue<T>(redisValue);
        }

        public async Task<T> StringGetSetExpiry<T>(string key, DateTime expiry, int db = -1, CancellationToken cancellationToken = default)
            where T : class
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return null;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue redisValue = await database.StringGetSetExpiryAsync(redisKey, expiry);

            return ConvertRedisValueToObjectValue<T>(redisValue);
        }

        public async Task<(T? Value, TimeSpan? Expiry)> StringGetWithExpiryForValueType<T>(string key, int db = -1, CancellationToken cancellationToken = default)
            where T : struct
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return (default, default);

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValueWithExpiry redisValueWithExpiry = await database.StringGetWithExpiryAsync(redisKey);

            return (ConvertRedisValueToValueTypeValue<T>(redisValueWithExpiry.Value), redisValueWithExpiry.Expiry);
        }

        public async Task<(T Value, TimeSpan? Expiry)> StringGetWithExpiry<T>(string key, int db = -1, CancellationToken cancellationToken = default)
            where T : class
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return (default, default);

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValueWithExpiry redisValueWithExpiry = await database.StringGetWithExpiryAsync(redisKey);

            return (ConvertRedisValueToObjectValue<T>(redisValueWithExpiry.Value), redisValueWithExpiry.Expiry);
        }
    }
}
